﻿//////////////////////////////////////////////////////////////////////////////
//This file is part of My Nes                                               //
//A Nintendo Entertainment System Emulator.                                 //
//                                                                          //
//Copyright © 2009 Ala Hadid (AHD)                                          //
//                                                                          //
//My Nes is free software; you can redistribute it and/or modify            //
//it under the terms of the GNU General Public License as published by      //
//the Free Software Foundation; either version 2 of the License, or         //
//(at your option) any later version.                                       //
//                                                                          //
//My Nes is distributed in the hope that it will be useful,                 //
//but WITHOUT ANY WARRANTY; without even the implied warranty of            //
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
//GNU General Public License for more details.                              //
//                                                                          //
//You should have received a copy of the GNU General Public License         //
//along with this program; if not, write to the Free Software               //
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA//
//////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AHD.MyNes.Nes
{
    using System.IO;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using FluxJpeg.Core;
    using Image = System.Windows.Controls.Image;

    public class Video
    {
        private readonly Image image;
        private WriteableBitmap bmp = new WriteableBitmap(256 * 2, 240 * 2);
        DrawSize _Size = DrawSize.Stretch;
        DrawFilter _Filter = DrawFilter.NearestNeighbor;
        DrawBits _Bits = DrawBits.Bit_32;
        public int Alpha = 150;//Works only with 32 bit mode
        public ushort RedMask = 0xF800;
        public ushort GreenMask = 0x7E0;
        public ushort BlueMask = 0x1F;
        bool IsTakingScreenShot = false;
        bool IsRedering = false;
        int[] imageBuffer = new int[256 * 240];
        int _FirstLinesTCut = 0;

        public int FirstLinesTCut
        { get { return _FirstLinesTCut; } set { _FirstLinesTCut = value; } }

        public Video(Image image)
        {
            this.image = image;
            image.Source = Bmp;
        }

        private bool x2Sampling = true;

        public bool X2Sampling
        {
            get { return x2Sampling; }
            set
            {
                if (x2Sampling != value)
                {
                    x2Sampling = value;
                    ChangeSampling();
                }

            }
        }

        private void ChangeSampling()
        {
            lock (Bmp)
            {
                if (x2Sampling)
                {
                    bmp = new WriteableBitmap(256 * 2, 240 * 2);
                }
                else
                {
                    bmp = new WriteableBitmap(256, 240);
                }
            }
        }

        public void RenderFrame(short[] ScreenBuffer)
        {
            if (!IsTakingScreenShot)
            {
                IsRedering = true;
                for (int i = 0; i < ScreenBuffer.Length; i++)
                {
                    //Extract the colors of each pixel to applay masks
                    //And convert to 32 bit color
                    byte RedValue = (byte)(((ScreenBuffer[i] & RedMask) >> 11) << 3);
                    byte GreenValue = (byte)(((ScreenBuffer[i] & GreenMask) >> 5) << 2);
                    byte BlueValue = (byte)((ScreenBuffer[i] & BlueMask) << 3);
                    int rgb = RedValue * 65536 + GreenValue * 256 + BlueValue | 0xff << 24;

                    imageBuffer[i] = rgb;
                }
                lock (Bmp)
                {



                    if (x2Sampling)
                    {
                        int currentBuffer = 0;
                        //Perform scale 2x
                        for (int i = 0; i < imageBuffer.Length; i++)
                        {
                            //Get surrounding pixels
                            int E = imageBuffer[i];
                            int B = (i - 256) >= 0
                                        ? imageBuffer[i - 256]
                                        : 0;
                            //-scanline
                            int H = (i + 256) < imageBuffer.Length
                                        ? imageBuffer[i + 256]
                                        : 0; //+scanline
                            int D = (i - 1) >= 0 ? imageBuffer[i - 1] : 0;
                            int F = (i + 1) < imageBuffer.Length
                                        ? imageBuffer[i + 1]
                                        : 0;

                            int E0, E1, E2, E3;

                            if (B != H && D != F)
                            {
                                E0 = D == B ? D : E;
                                E1 = B == F ? F : E;
                                E2 = D == H ? D : E;
                                E3 = H == F ? F : E;
                            }
                            else
                            {
                                E0 = E;
                                E1 = E;
                                E2 = E;
                                E3 = E;
                            }
                            Bmp.Pixels[currentBuffer] = E0;
                            Bmp.Pixels[currentBuffer + 1] = E1;
                            //Next line
                            Bmp.Pixels[currentBuffer + 256 * 2] = E2;
                            Bmp.Pixels[currentBuffer + 256 * 2 + 1] = E3;
                            currentBuffer += 2;
                            if (currentBuffer % (256 * 2) == 0)
                            {
                                //Scan line reached
                                currentBuffer += 256 * 2; //skip next line
                            }
                            if (currentBuffer == Bmp.Pixels.Length)
                                break;
                        }

                    }
                    else
                    {
                        //Raw copy
                        Array.Copy(imageBuffer, Bmp.Pixels, imageBuffer.Length);
                    }
                    


                }
                IsRedering = false;

            }
        }

        private void Refresh()
        {
            Bmp.Invalidate();
        }

        /// <summary>
        /// Get or set the draw size
        /// </summary>
        public DrawSize Size
        {
            get { return _Size; }
            set
            {
                _Size = value;
            }
        }
        /// <summary>
        /// Get or set the draw filter
        /// </summary>
        public DrawFilter Filter
        { get { return _Filter; } set { _Filter = value; } }
        /// <summary>
        /// Get or set the draw mode
        /// </summary>
        public DrawBits DrawBit
        { get { return _Bits; } set { _Bits = value; } }

        public WriteableBitmap Bmp
        {
            get { return bmp; }
        }

        /// <summary>
        /// Save current screenshot
        /// </summary>
        /// <param name="ScreenshotPath">The full path of screenshot to save into</param>
        /// <param name="Format">The extension of the format (e.g. : .bmp means Bmp format)</param>
        public void TakeScreenShot(Stream stream)
        {
            IsTakingScreenShot = true;
            //Whait for the current frame
            while (IsRedering)
            {

            }
            //Save
            SaveToStream(stream);
            IsTakingScreenShot = false;
        }

        public int[] GetContents()
        {
            IsTakingScreenShot = true;
            //Whait for the current frame
            while (IsRedering)
            {

            }
            //Save
            IsTakingScreenShot = false;
            return imageBuffer;
        }

        private void SaveToStream(Stream fs)
        {
            int width = 256;
            int height = 240;
            int bands = 3;
            byte[][,] raster = new byte[bands][,];

            //Convert the Image to pass into FJCore
            //Code From http://stackoverflow.com/questions/1139200/using-fjcore-to-encode-silverlight-writeablebitmap
            for (int i = 0; i < bands; i++)
            {
                raster[i] = new byte[width, height];
            }

            for (int row = 0; row < height; row++)
            {
                for (int column = 0; column < width; column++)
                {
                    int pixel = imageBuffer[width * row + column];
                    raster[0][column, row] = (byte)(pixel >> 16);
                    raster[1][column, row] = (byte)(pixel >> 8);
                    raster[2][column, row] = (byte)pixel;
                }
            }

            ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
            FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

            //Encode the Image as a JPEG
            using (MemoryStream stream = new MemoryStream())
            {
                FluxJpeg.Core.Encoder.JpegEncoder encoder = new FluxJpeg.Core.Encoder.JpegEncoder(img,
                                                                                                  100,
                                                                                                  stream);
                encoder.Encode();

                //Back to the start
                stream.Seek(0, SeekOrigin.Begin);

                //Get teh Bytes and write them to the stream
                byte[] binaryData = stream.GetBuffer();
                fs.Write(binaryData, 0, binaryData.Length);
            }
        }

    }
    public enum DrawSize
    {
        Stretch, X1, X2
    }
    public enum DrawFilter
    {
        NearestNeighbor,
        Default
    }
    public enum DrawBits
    {
        Bit_16,
        Bit_32
    }
}
